package jannex.mapping;

import jannex.Builder;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MappingContext {

    private final Map<Class<?>, Mapping<?>> mappings;
    private final Map<Class<?>, ResultSetHandler<?>> handlers;
    private final Map<Class<?>, Builder<?>> objectBuilders;

    private final MappingFactory mappingFactory;
    private final ResultSetHandlerFactory resultSetHandlerFactory;
    private final BuilderFactory builderFactory;

    private final boolean statelessMapping;

    public MappingContext(MappingFactory mappingFactory, boolean statelessMapping) {

        this.statelessMapping = statelessMapping;

        this.mappingFactory = mappingFactory;
        this.resultSetHandlerFactory = new ResultSetHandlerFactory(this);
        this.builderFactory = new BuilderFactory();

        Map<Class<?>, ResultSetHandler<?>> defaultHandlers = new HashMap<>();
        defaultHandlers.put(List.class, Handlers.list());
        defaultHandlers.put(Map.class, Handlers.map());

        for (Class<?> cls : new Class[] {
                Long.class, long.class, Integer.class, int.class,
                Short.class, short.class, Byte.class, byte.class,
                Double.class, double.class, Float.class, float.class,
                Boolean.class, boolean.class, Character.class, char.class,
                String.class, Date.class, Time.class, Timestamp.class}) {
            defaultHandlers.put(cls, Handlers.first());
        }

        if (statelessMapping) {
            this.handlers = defaultHandlers;
            this.mappings = new HashMap<>();
            this.objectBuilders = new HashMap<>();
        } else {
            this.handlers = new ConcurrentHashMap<>(defaultHandlers);
            this.mappings = new ConcurrentHashMap<>();
            this.objectBuilders = new ConcurrentHashMap<>();
        }
    }

    public <E> void addHandler(Class<E> elementClass, ResultSetHandler<E> handler) {
        if (!statelessMapping) {
            handlers.put(elementClass, handler);
        }
    }

    public <E> void addMapping(Class<E> elementClass, Mapping<E> mapping) {
        if (!statelessMapping) {
            mappings.put(elementClass, mapping);
        }
    }

    public <E> void addBuilder(Class<E> elementClass, Builder<E> builder) {
        if (!statelessMapping) {
            objectBuilders.put(elementClass, builder);
        }
    }

    @SuppressWarnings("unchecked")
    public <E> ResultSetHandler<E> getHandler(Class<E> elementClass) {
        @SuppressWarnings("unchecked")
        ResultSetHandler<E> handler = (ResultSetHandler<E>) handlers.get(elementClass);
        if (handler == null) {
            handler = resultSetHandlerFactory.create(getMapping(elementClass), getBuilder(elementClass));
            addHandler(elementClass, handler);
        }
        return handler;
    }

    public <E> Mapping<E> getMapping(Class<E> elementClass) {
        @SuppressWarnings("unchecked")
        Mapping<E> mapping = (Mapping<E>) mappings.get(elementClass);
        if (mapping == null) {
            mapping = mappingFactory.create(elementClass);
            addMapping(elementClass, mapping);
        }
        return mapping;
    }


    public <E> Builder<E> getBuilder(Class<E> elementClass) {
        @SuppressWarnings("unchecked")
        Builder<E> builder = (Builder<E>) objectBuilders.get(elementClass);
        if (builder == null) {
            builder = builderFactory.create(elementClass);
            addBuilder(elementClass, builder);
        }
        return builder;
    }
}
